/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                        Intel License Agreement
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of Intel Corporation may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "precomp.hpp"

#if 0

ML_IMPL int
icvCmpIntegers (const void* a, const void* b) {return *(const int*)a - *(const int*)b;}

/****************************************************************************************\
*                    Cross-validation algorithms realizations                            *
\****************************************************************************************/

// Return pointer to trainIdx. Function DOES NOT FILL this matrix!
ML_IMPL
const CvMat* cvCrossValGetTrainIdxMatrix (const CvStatModel* estimateModel) {
	CvMat* result = NULL;

	CV_FUNCNAME ("cvCrossValGetTrainIdxMatrix");
	__BEGIN__

	if (!CV_IS_CROSSVAL(estimateModel)) {
		CV_ERROR (CV_StsBadArg, "Pointer point to not CvCrossValidationModel");
	}

	result = ((CvCrossValidationModel*)estimateModel)->sampleIdxTrain;

	__END__

	return result;
} // End of cvCrossValGetTrainIdxMatrix

/****************************************************************************************/
// Return pointer to checkIdx. Function DOES NOT FILL this matrix!
ML_IMPL
const CvMat* cvCrossValGetCheckIdxMatrix (const CvStatModel* estimateModel) {
	CvMat* result = NULL;

	CV_FUNCNAME ("cvCrossValGetCheckIdxMatrix");
	__BEGIN__

	if (!CV_IS_CROSSVAL (estimateModel)) {
		CV_ERROR (CV_StsBadArg, "Pointer point to not CvCrossValidationModel");
	}

	result = ((CvCrossValidationModel*)estimateModel)->sampleIdxEval;

	__END__

	return result;
} // End of cvCrossValGetCheckIdxMatrix

/****************************************************************************************/
// Create new Idx-matrix for next classifiers training and return code of result.
//   Result is 0 if function can't make next step (error input or folds are finished),
//   it is 1 if all was correct, and it is 2 if current fold wasn't' checked.
ML_IMPL
int cvCrossValNextStep (CvStatModel* estimateModel) {
	int result = 0;

	CV_FUNCNAME ("cvCrossValGetNextTrainIdx");
	__BEGIN__

	CvCrossValidationModel* crVal = (CvCrossValidationModel*) estimateModel;
	int k, fold;

	if (!CV_IS_CROSSVAL (estimateModel)) {
		CV_ERROR (CV_StsBadArg, "Pointer point to not CvCrossValidationModel");
	}

	fold = ++crVal->current_fold;

	if (fold >= crVal->folds_all) {
		if (fold == crVal->folds_all) {
			EXIT;
		} else {
			CV_ERROR (CV_StsInternal, "All iterations has end long ago");
		}
	}

	k = crVal->folds[fold + 1] - crVal->folds[fold];
	crVal->sampleIdxTrain->data.i = crVal->sampleIdxAll + crVal->folds[fold + 1];
	crVal->sampleIdxTrain->cols = crVal->samples_all - k;
	crVal->sampleIdxEval->data.i = crVal->sampleIdxAll + crVal->folds[fold];
	crVal->sampleIdxEval->cols = k;

	if (crVal->is_checked) {
		crVal->is_checked = 0;
		result = 1;
	} else {
		result = 2;
	}

	__END__

	return result;
}

/****************************************************************************************/
// Do checking part of loop  of cross-validations metod.
ML_IMPL
void cvCrossValCheckClassifier (CvStatModel*  estimateModel,
								const CvStatModel*  model,
								const CvMat*        trainData,
								int           sample_t_flag,
								const CvMat*        trainClasses) {
	CV_FUNCNAME ("cvCrossValCheckClassifier ");
	__BEGIN__

	CvCrossValidationModel* crVal = (CvCrossValidationModel*) estimateModel;
	int  i, j, k;
	int* data;
	float* responses_fl;
	int    step;
	float* responses_result;
	int* responses_i;
	double te, te1;
	double sum_c, sum_p, sum_pp, sum_cp, sum_cc, sq_err;

// Check input data to correct values.
	if (!CV_IS_CROSSVAL (estimateModel)) {
		CV_ERROR (CV_StsBadArg, "First parameter point to not CvCrossValidationModel");
	}
	if (!CV_IS_STAT_MODEL (model)) {
		CV_ERROR (CV_StsBadArg, "Second parameter point to not CvStatModel");
	}
	if (!CV_IS_MAT (trainData)) {
		CV_ERROR (CV_StsBadArg, "Third parameter point to not CvMat");
	}
	if (!CV_IS_MAT (trainClasses)) {
		CV_ERROR (CV_StsBadArg, "Fifth parameter point to not CvMat");
	}
	if (crVal->is_checked) {
		CV_ERROR (CV_StsInternal, "This iterations already was checked");
	}

// Initialize.
	k = crVal->sampleIdxEval->cols;
	data = crVal->sampleIdxEval->data.i;

// Eval tested feature vectors.
	CV_CALL (cvStatModelMultiPredict (model, trainData, sample_t_flag,
									  crVal->predict_results, NULL, crVal->sampleIdxEval));
// Count number if correct results.
	responses_result = crVal->predict_results->data.fl;
	if (crVal->is_regression) {
		sum_c = sum_p = sum_pp = sum_cp = sum_cc = sq_err = 0;
		if (CV_MAT_TYPE (trainClasses->type) == CV_32FC1) {
			responses_fl = trainClasses->data.fl;
			step = trainClasses->rows == 1 ? 1 : trainClasses->step / sizeof(float);
			for (i = 0; i < k; i++) {
				te = responses_result[*data];
				te1 = responses_fl[*data * step];
				sum_c += te1;
				sum_p += te;
				sum_cc += te1 * te1;
				sum_pp += te * te;
				sum_cp += te1 * te;
				te -= te1;
				sq_err += te  * te;

				data++;
			}
		} else {
			responses_i = trainClasses->data.i;
			step = trainClasses->rows == 1 ? 1 : trainClasses->step / sizeof(int);
			for (i = 0; i < k; i++) {
				te = responses_result[*data];
				te1 = responses_i[*data * step];
				sum_c += te1;
				sum_p += te;
				sum_cc += te1 * te1;
				sum_pp += te * te;
				sum_cp += te1 * te;
				te -= te1;
				sq_err += te  * te;

				data++;
			}
		}
		// Fixing new internal values of accuracy.
		crVal->sum_correct += sum_c;
		crVal->sum_predict += sum_p;
		crVal->sum_cc += sum_cc;
		crVal->sum_pp += sum_pp;
		crVal->sum_cp += sum_cp;
		crVal->sq_error += sq_err;
	} else {
		if (CV_MAT_TYPE (trainClasses->type) == CV_32FC1) {
			responses_fl = trainClasses->data.fl;
			step = trainClasses->rows == 1 ? 1 : trainClasses->step / sizeof(float);
			for (i = 0, j = 0; i < k; i++) {
				if (cvRound (responses_result[*data]) == cvRound (responses_fl[*data * step])) {
					j++;
				}
				data++;
			}
		} else {
			responses_i = trainClasses->data.i;
			step = trainClasses->rows == 1 ? 1 : trainClasses->step / sizeof(int);
			for (i = 0, j = 0; i < k; i++) {
				if (cvRound (responses_result[*data]) == responses_i[*data * step]) {
					j++;
				}
				data++;
			}
		}
		// Fixing new internal values of accuracy.
		crVal->correct_results += j;
	}
// Fixing that this fold already checked.
	crVal->all_results += k;
	crVal->is_checked = 1;

	__END__
} // End of cvCrossValCheckClassifier

/****************************************************************************************/
// Return current accuracy.
ML_IMPL
float cvCrossValGetResult (const CvStatModel* estimateModel,
						   float*       correlation) {
	float result = 0;

	CV_FUNCNAME ("cvCrossValGetResult");
	__BEGIN__

	double te, te1;
	CvCrossValidationModel* crVal = (CvCrossValidationModel*)estimateModel;

	if (!CV_IS_CROSSVAL (estimateModel)) {
		CV_ERROR (CV_StsBadArg, "Pointer point to not CvCrossValidationModel");
	}

	if (crVal->all_results) {
		if (crVal->is_regression) {
			result = ((float)crVal->sq_error) / crVal->all_results;
			if (correlation) {
				te = crVal->all_results * crVal->sum_cp -
					 crVal->sum_correct * crVal->sum_predict;
				te *= te;
				te1 = (crVal->all_results * crVal->sum_cc -
					   crVal->sum_correct * crVal->sum_correct) *
					  (crVal->all_results * crVal->sum_pp -
					   crVal->sum_predict * crVal->sum_predict);
				*correlation = (float)(te / te1);

			}
		} else {
			result = ((float)crVal->correct_results) / crVal->all_results;
		}
	}

	__END__

	return result;
}

/****************************************************************************************/
// Reset cross-validation EstimateModel to state the same as it was immidiatly after
//   its creating.
ML_IMPL
void cvCrossValReset (CvStatModel* estimateModel) {
	CV_FUNCNAME ("cvCrossValReset");
	__BEGIN__

	CvCrossValidationModel* crVal = (CvCrossValidationModel*)estimateModel;

	if (!CV_IS_CROSSVAL (estimateModel)) {
		CV_ERROR (CV_StsBadArg, "Pointer point to not CvCrossValidationModel");
	}

	crVal->current_fold = -1;
	crVal->is_checked = 1;
	crVal->all_results = 0;
	crVal->correct_results = 0;
	crVal->sq_error = 0;
	crVal->sum_correct = 0;
	crVal->sum_predict = 0;
	crVal->sum_cc = 0;
	crVal->sum_pp = 0;
	crVal->sum_cp = 0;

	__END__
}

/****************************************************************************************/
// This function is standart CvStatModel field to release cross-validation EstimateModel.
ML_IMPL
void cvReleaseCrossValidationModel (CvStatModel** model) {
	CvCrossValidationModel* pModel;

	CV_FUNCNAME ("cvReleaseCrossValidationModel");
	__BEGIN__

	if (!model) {
		CV_ERROR (CV_StsNullPtr, "");
	}

	pModel = (CvCrossValidationModel*) * model;
	if (!pModel) {
		return;
	}
	if (!CV_IS_CROSSVAL (pModel)) {
		CV_ERROR (CV_StsBadArg, "");
	}

	cvFree (&pModel->sampleIdxAll);
	cvFree (&pModel->folds);
	cvReleaseMat (&pModel->sampleIdxEval);
	cvReleaseMat (&pModel->sampleIdxTrain);
	cvReleaseMat (&pModel->predict_results);

	cvFree (model);

	__END__
} // End of cvReleaseCrossValidationModel.

/****************************************************************************************/
// This function create cross-validation EstimateModel.
ML_IMPL CvStatModel*
cvCreateCrossValidationEstimateModel(
	int                samples_all,
	const CvStatModelParams* estimateParams,
	const CvMat*             sampleIdx) {
	CvStatModel*            model   = NULL;
	CvCrossValidationModel* crVal   = NULL;

	CV_FUNCNAME ("cvCreateCrossValidationEstimateModel");
	__BEGIN__

	int  k_fold = 10;

	int  i, j, k, s_len;
	int  samples_selected;
	CvRNG rng;
	CvRNG* prng;
	int* res_s_data;
	int* te_s_data;
	int* folds;

	rng = cvRNG(cvGetTickCount());
	cvRandInt (&rng); cvRandInt (&rng); cvRandInt (&rng); cvRandInt (&rng);
// Check input parameters.
	if (estimateParams) {
		k_fold = ((CvCrossValidationParams*)estimateParams)->k_fold;
	}
	if (!k_fold) {
		CV_ERROR (CV_StsBadArg, "Error in parameters of cross-validation (k_fold == 0)!");
	}
	if (samples_all <= 0) {
		CV_ERROR (CV_StsBadArg, "<samples_all> should be positive!");
	}

// Alloc memory and fill standart StatModel's fields.
	CV_CALL (crVal = (CvCrossValidationModel*)cvCreateStatModel (
						 CV_STAT_MODEL_MAGIC_VAL | CV_CROSSVAL_MAGIC_VAL,
						 sizeof(CvCrossValidationModel),
						 cvReleaseCrossValidationModel,
						 NULL, NULL));
	crVal->current_fold    = -1;
	crVal->folds_all       = k_fold;
	if (estimateParams && ((CvCrossValidationParams*)estimateParams)->is_regression) {
		crVal->is_regression = 1;
	} else {
		crVal->is_regression = 0;
	}
	if (estimateParams && ((CvCrossValidationParams*)estimateParams)->rng) {
		prng = ((CvCrossValidationParams*)estimateParams)->rng;
	} else {
		prng = &rng;
	}

	// Check and preprocess sample indices.
	if (sampleIdx) {
		int s_step;
		int s_type = 0;

		if (!CV_IS_MAT (sampleIdx)) {
			CV_ERROR (CV_StsBadArg, "Invalid sampleIdx array");
		}

		if (sampleIdx->rows != 1 && sampleIdx->cols != 1) {
			CV_ERROR (CV_StsBadSize, "sampleIdx array must be 1-dimensional");
		}

		s_len = sampleIdx->rows + sampleIdx->cols - 1;
		s_step = sampleIdx->rows == 1 ?
				 1 : sampleIdx->step / CV_ELEM_SIZE(sampleIdx->type);

		s_type = CV_MAT_TYPE (sampleIdx->type);

		switch (s_type) {
		case CV_8UC1:
		case CV_8SC1: {
			uchar* s_data = sampleIdx->data.ptr;

			// sampleIdx is array of 1's and 0's -
			// i.e. it is a mask of the selected samples
			if ( s_len != samples_all )
				CV_ERROR (CV_StsUnmatchedSizes,
						  "Sample mask should contain as many elements as the total number of samples");

			samples_selected = 0;
			for (i = 0; i < s_len; i++) {
				samples_selected += s_data[i* s_step] != 0;
			}

			if (samples_selected == 0) {
				CV_ERROR (CV_StsOutOfRange, "No samples is selected!");
			}
		}
		s_len = samples_selected;
		break;
		case CV_32SC1:
			if (s_len > samples_all)
				CV_ERROR (CV_StsOutOfRange,
						  "sampleIdx array may not contain more elements than the total number of samples");
			samples_selected = s_len;
			break;
		default:
			CV_ERROR (CV_StsUnsupportedFormat, "Unsupported sampleIdx array data type "
					  "(it should be 8uC1, 8sC1 or 32sC1)");
		}

		// Alloc additional memory for internal Idx and fill it.
		/*!!*/  CV_CALL (res_s_data = crVal->sampleIdxAll =
										  (int*)cvAlloc (2 * s_len * sizeof(int)));

		if (s_type < CV_32SC1) {
			uchar* s_data = sampleIdx->data.ptr;
			for (i = 0; i < s_len; i++)
				if (s_data[i * s_step]) {
					*res_s_data++ = i;
				}
			res_s_data = crVal->sampleIdxAll;
		} else {
			int* s_data = sampleIdx->data.i;
			int out_of_order = 0;

			for (i = 0; i < s_len; i++) {
				res_s_data[i] = s_data[i * s_step];
				if (i > 0 && res_s_data[i] < res_s_data[i - 1]) {
					out_of_order = 1;
				}
			}

			if (out_of_order) {
				qsort (res_s_data, s_len, sizeof(res_s_data[0]), icvCmpIntegers);
			}

			if (res_s_data[0] < 0 ||
					res_s_data[s_len - 1] >= samples_all) {
				CV_ERROR (CV_StsBadArg, "There are out-of-range sample indices");
			}
			for (i = 1; i < s_len; i++)
				if (res_s_data[i] <= res_s_data[i - 1]) {
					CV_ERROR (CV_StsBadArg, "There are duplicated");
				}
		}
	} else { // if (sampleIdx)
		// Alloc additional memory for internal Idx and fill it.
		s_len = samples_all;
		CV_CALL (res_s_data = crVal->sampleIdxAll = (int*)cvAlloc (2 * s_len * sizeof(int)));
		for (i = 0; i < s_len; i++) {
			*res_s_data++ = i;
		}
		res_s_data = crVal->sampleIdxAll;
	} // if (sampleIdx) ... else

// Resort internal Idx.
	te_s_data = res_s_data + s_len;
	for (i = s_len; i > 1; i--) {
		j = cvRandInt (prng) % i;
		k = *(--te_s_data);
		*te_s_data = res_s_data[j];
		res_s_data[j] = k;
	}

// Duplicate resorted internal Idx.
// It will be used to simplify operation of getting trainIdx.
	te_s_data = res_s_data + s_len;
	for (i = 0; i < s_len; i++) {
		*te_s_data++ = *res_s_data++;
	}

// Cut sampleIdxAll to parts.
	if (k_fold > 0) {
		if (k_fold > s_len) {
			CV_ERROR (CV_StsBadArg,
					  "Error in parameters of cross-validation ('k_fold' > #samples)!");
		}
		folds = crVal->folds = (int*) cvAlloc ((k_fold + 1) * sizeof (int));
		*folds++ = 0;
		for (i = 1; i < k_fold; i++) {
			*folds++ = cvRound (i * s_len * 1. / k_fold);
		}
		*folds = s_len;
		folds = crVal->folds;

		crVal->max_fold_size = (s_len - 1) / k_fold + 1;
	} else {
		k = -k_fold;
		crVal->max_fold_size = k;
		if (k >= s_len) {
			CV_ERROR (CV_StsBadArg,
					  "Error in parameters of cross-validation (-'k_fold' > #samples)!");
		}
		crVal->folds_all = k = (s_len - 1) / k + 1;

		folds = crVal->folds = (int*) cvAlloc ((k + 1) * sizeof (int));
		for (i = 0; i < k; i++) {
			*folds++ = -i * k_fold;
		}
		*folds = s_len;
		folds = crVal->folds;
	}

// Prepare other internal fields to working.
	CV_CALL (crVal->predict_results = cvCreateMat (1, samples_all, CV_32FC1));
	CV_CALL (crVal->sampleIdxEval = cvCreateMatHeader (1, 1, CV_32SC1));
	CV_CALL (crVal->sampleIdxTrain = cvCreateMatHeader (1, 1, CV_32SC1));
	crVal->sampleIdxEval->cols = 0;
	crVal->sampleIdxTrain->cols = 0;
	crVal->samples_all = s_len;
	crVal->is_checked = 1;

	crVal->getTrainIdxMat = cvCrossValGetTrainIdxMatrix;
	crVal->getCheckIdxMat = cvCrossValGetCheckIdxMatrix;
	crVal->nextStep = cvCrossValNextStep;
	crVal->check = cvCrossValCheckClassifier;
	crVal->getResult = cvCrossValGetResult;
	crVal->reset = cvCrossValReset;

	model = (CvStatModel*)crVal;

	__END__

	if (!model) {
		cvReleaseCrossValidationModel ((CvStatModel**)&crVal);
	}

	return model;
} // End of cvCreateCrossValidationEstimateModel


/****************************************************************************************\
*                Extended interface with backcalls for models                            *
\****************************************************************************************/
ML_IMPL float
cvCrossValidation (const CvMat*            trueData,
				   int               tflag,
				   const CvMat*            trueClasses,
				   CvStatModel*     (*createClassifier) (const CvMat*,
						   int,
						   const CvMat*,
						   const CvClassifierTrainParams*,
						   const CvMat*,
						   const CvMat*,
						   const CvMat*,
						   const CvMat*),
				   const CvClassifierTrainParams*    estimateParams,
				   const CvClassifierTrainParams*    trainParams,
				   const CvMat*            compIdx,
				   const CvMat*            sampleIdx,
				   CvStatModel**     pCrValModel,
				   const CvMat*            typeMask,
				   const CvMat*            missedMeasurementMask) {
	CvCrossValidationModel* crVal = NULL;
	float  result = 0;
	CvStatModel* pClassifier = NULL;

	CV_FUNCNAME ("cvCrossValidation");
	__BEGIN__

	const CvMat* trainDataIdx;
	int    samples_all;

// checking input data
	if ((createClassifier) == NULL) {
		CV_ERROR (CV_StsNullPtr, "Null pointer to functiion which create classifier");
	}
	if (pCrValModel && *pCrValModel && !CV_IS_CROSSVAL(*pCrValModel)) {
		CV_ERROR (CV_StsBadArg,
				  "<pCrValModel> point to not cross-validation model");
	}

// initialization
	if (pCrValModel && *pCrValModel) {
		crVal = (CvCrossValidationModel*) * pCrValModel;
		crVal->reset ((CvStatModel*)crVal);
	} else {
		samples_all = ((tflag) ? trueData->rows : trueData->cols);
		CV_CALL (crVal = (CvCrossValidationModel*)
						 cvCreateCrossValidationEstimateModel (samples_all, estimateParams, sampleIdx));
	}

	CV_CALL (trainDataIdx = crVal->getTrainIdxMat ((CvStatModel*)crVal));

// operation loop
	for (; crVal->nextStep((CvStatModel*)crVal) != 0; ) {
		CV_CALL (pClassifier = createClassifier (trueData, tflag, trueClasses,
							   trainParams, compIdx, trainDataIdx, typeMask, missedMeasurementMask));
		CV_CALL (crVal->check ((CvStatModel*)crVal, pClassifier,
							   trueData, tflag, trueClasses));

		pClassifier->release (&pClassifier);
	}

// Get result and fill output field.
	CV_CALL (result = crVal->getResult ((CvStatModel*)crVal, 0));

	if (pCrValModel && !*pCrValModel) {
		*pCrValModel = (CvStatModel*)crVal;
	}

	__END__

// Free all memory that should be freed.
	if (pClassifier) {
		pClassifier->release (&pClassifier);
	}
	if (crVal && (!pCrValModel || !*pCrValModel)) {
		crVal->release ((CvStatModel**)&crVal);
	}

	return result;
} // End of cvCrossValidation

#endif

/* End of file */
